<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>直线解析式求解工具</title>
    <style>
      * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
      }
      body {
        background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
        color: #333;
        height: 100vh;
        width: 100vw;
        padding: 20px;
      }
      .container {
        width: 100%;
        height: 100%;
        border-radius: 15px;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
        overflow: auto;
        background-color: #fff;
      }
      canvas {
        border: 1px solid;
        margin: 30px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <button id="resetBtn">重置画布</button>
      函数解析式：
      <textarea name="" id="formulaTxt" readonly></textarea>
      <br />
      <canvas width="800" height="800"></canvas>
    </div>
  </body>
</html>
<script>
  const resetBtn = document.getElementById("resetBtn");
  const formulaTxt = document.getElementById("formulaTxt");

  resetBtn.onclick = () => {
    initCoordinate();

    // 清空所有的存储点
    clickPointStore = [];
    realPointStore = [];
    newPointArrStore = [];
    newRealPointArrStore = [];

    // 清空函数解析式
    formulaTxt.value = "";
  };

  const canvas = document.querySelector("canvas");
  const ctx = canvas.getContext("2d");

  let clickPointStore = []; // 存储点击的点的坐标
  let realPointStore = []; // 存储实际在 canvas 中的坐标
  let newPointArrStore = [];
  let newRealPointArrStore = [];

  canvas.addEventListener("click", (ev) => {
    const position = canvas.getBoundingClientRect();
    const xx = (ev.clientX - position.left).toFixed(0) - 0;
    const yy = (ev.clientY - position.top).toFixed(0) - 0;
    const realInCanvasPoint = { x: xx, y: yy }; // 点击的点的实际在 canvas 中的坐标
    const clickPoint = transformPointBack(realInCanvasPoint); // 点击的点的实际在数学坐标系中的坐标
    clickPointStore.push(clickPoint);
    realPointStore.push(realInCanvasPoint);
    if (clickPointStore.length > 2 || newPointArrStore.length > 2) {
      if (!isHaveRepeatPoint(clickPointStore)) {
        clickPointStore.shift(); // 如果点集长度大于 2，则删除第一个点，即保留最后两个点
        realPointStore.shift();
      } else {
        clickPointStore.pop(); // 如果点集长度大于 2，且最后两个点相同，则删除最后一个点
        realPointStore.pop();
      }
    }
    newPointArrStore = deleteRepeatPoint(clickPointStore);
    newRealPointArrStore = deleteRepeatPoint(realPointStore);
    initCoordinate();
    const distance = 75;
    newPointArrStore.forEach((point, i) => {
      const positionBoundary = boundaryTest(
        transformToCanvasPoint(point),
        distance
      );
      // 写值的时候要用数学坐标系中的坐标，但是值的位置也要是在 canvas 中的坐标
      switch (positionBoundary) {
        case "rightTop":
        case "right":
          writeText(
            `(${point.x}, ${point.y})`,
            newRealPointArrStore[i].x - distance,
            newRealPointArrStore[i].y + 10
          );
          break;
        case "rightBottom":
        case "bottom":
          writeText(
            `(${point.x}, ${point.y})`,
            newRealPointArrStore[i].x - distance,
            newRealPointArrStore[i].y - 10
          );
          break;
        case "leftBottom":
          writeText(
            `(${point.x}, ${point.y})`,
            newRealPointArrStore[i].x + 10,
            newRealPointArrStore[i].y - 10
          );
          break;
        default:
          writeText(
            `(${point.x}, ${point.y})`,
            newRealPointArrStore[i].x + 10,
            newRealPointArrStore[i].y + 10
          );
          break;
      }
      // 绘制点的时候要用 canvas 中的坐标
      drawPoint(ctx, newRealPointArrStore[i], 5, "#e74c3c");
    });
    // 画直线
    if (
      newRealPointArrStore &&
      newRealPointArrStore.length &&
      newRealPointArrStore.length != 1
    ) {
      drawLine(newRealPointArrStore);
      const params = resolveLineParamsFunction(clickPointStore);
      formulaTxt.value =
        resolveFormula(params.A.toFixed(4) - 0, "x", "head") +
        resolveFormula(params.B.toFixed(4) - 0, "y") +
        resolveFormula(params.C.toFixed(4) - 0, "") +
        "=0";
    }
  });

  // 规范等式
  function resolveFormula(num, text, position = "other") {
    if (Math.abs(num) == 1) {
      if (text) {
        return num > 0 ? `${text}` : `-${text}`;
      } else {
        if (position === "head") {
          return `${num}`;
        } else {
          return num > 0 ? `+1` : `-1`;
        }
      }
    }
    if (!num) {
      return "";
    } else if (num > 0) {
      return position === "head" ? `${num}${text}` : `+${num}${text}`;
    } else {
      return `${num}${text}`;
    }
  }

  // 初始化坐标轴画布
  function initCoordinate() {
    clearCanvas();
    drawCoordinate(ctx);

    // other
    const po = { x: 100, y: 200 };
    drawCircle(ctx, transformToCanvasPoint(po), 10);
  }

  // 画线（线段，从一点到另一点）
  function drawLineSegment(pointArr, color = "#333") {
    if (!pointArr || !Array.isArray(pointArr)) return;
    if (pointArr.length < 2) return;
    ctx.save();
    ctx.beginPath();
    for (let i = 0; i < pointArr.length; i++) {
      i === 0
        ? ctx.moveTo(pointArr[0].x, pointArr[0].y)
        : ctx.lineTo(pointArr[i].x, pointArr[i].y);
    }
    ctx.strokeStyle = color;
    ctx.stroke();
    // ctx.closePath();
    ctx.restore();
  }

  // 画直线
  function drawLine(pointArr, color = "#333") {
    const params = resolveLineParamsFunction(pointArr);
    if (!params.B) {
      // 竖直线
      drawLineSegment(
        [
          {
            x: -params.C / params.A,
            y: 0,
          },
          {
            x: -params.C / params.A,
            y: canvas.height,
          },
        ],
        color
      );
      return;
    }
    // 其他情况，求出最左边点
    const leftPoint = {
      x: 0,
      y: -params.C / params.B,
    };
    // 求出最右边点
    const rightPoint = {
      x: canvas.width,
      y: -(params.A * canvas.width + params.C) / params.B,
    };
    drawLineSegment([leftPoint, rightPoint], color);
  }

  /**
   * @description: 筛选重复点，保留非重复点
   * @param {*} pointArr: 点集（$3.2）
   * @return [{ x, y }, ... ] 筛选后的点集（$3.2）
   */
  function deleteRepeatPoint(pointArr) {
    if (!pointArr) throw new Error("请输入合法点集参数！");
    if (pointArr.length < 1) throw new Error("输入的点至少要有 1 个！");
    if (pointArr.length === 1) return pointArr;
    // 利用 Set 去重
    // 思想：用快速查找的数据结构记录已经见过的点
    const seen = new Set();
    return pointArr.filter((point) => {
      const key = `${point.x},${point.y}`;
      if (seen.has(key)) return false;
      seen.add(key);
      return true;
    });
  }

  /**
   * @description: 判断点集里面是否有重复点
   * @param {*} pointArr: 点集（$3.2）
   * @return {boolean} 是否有重复点
   */
  function isHaveRepeatPoint(pointArr) {
    if (!pointArr) throw new Error("请输入合法点集参数！");
    if (pointArr.length < 1) throw new Error("输入的点至少要有 1 个！");
    if (pointArr.length === 1) return false;
    // 利用 Set 去重
    // 思想：用快速查找的数据结构记录已经见过的点
    const seen = new Set();
    for (let i = 0; i < pointArr.length; i++) {
      const key = `${pointArr[i].x},${pointArr[i].y}`;
      if (seen.has(key)) continue;
      seen.add(key);
    }

    return seen.size !== pointArr.length;
  }

  /**
   * @description: 求解直线解析式（ Ax + By + C = 0 ）
   * @param {*} pointArr: 坐标集（$3.2.2），直线上的任意两个点
   * @return { A, B, C } 返回直线解析式中的系数
   */
  function resolveLineParamsFunction(pointArr) {
    if (!pointArr || !Array.isArray(pointArr)) {
      throw new Error("请输入正确的点的格式！");
    }
    const newPointArr = deleteRepeatPoint(pointArr);
    if (newPointArr.length < 2) {
      throw new Error("直线由至少两个点确认或目前有点重复，请至少输入两个点！");
    }

    const epsilon = 1e-10; // 精度，用于判断浮点数是否相等
    // 如果是竖直线
    if (Math.abs(newPointArr[1].x - newPointArr[0].x) < epsilon) {
      return {
        A: 1,
        B: 0,
        C: -newPointArr[0].x,
      };
    }
    // 如果是水平线
    if (Math.abs(newPointArr[1].y - newPointArr[0].y) < epsilon) {
      return {
        A: 0,
        B: 1,
        C: -newPointArr[0].y,
      };
    }
    // 斜率，tan
    const k =
      (newPointArr[1].y - newPointArr[0].y) /
      (newPointArr[1].x - newPointArr[0].x);
    const b = newPointArr[0].y - k * newPointArr[0].x;
    return {
      A: k,
      B: -1,
      C: b,
    };
  }

  // 写文字
  function writeText(text, x, y, fontSize = 16, color = "#000") {
    ctx.save();
    ctx.beginPath();
    ctx.font = `${fontSize}px Times New Roman`;
    ctx.fillStyle = color;
    ctx.fillText(text, x, y);
    ctx.restore();
  }

  function useDrawLine(context, linePoint, canvasConfig = {}) {
    // 配置项，并设置默认值
    let {
      lineColor = "#000",
      fontSize = 13,
      pointSize = 4,
      arrowSize = 10,
      fontMaxWidth = 150,
      textOffset = "-5",
      lineWidth = 1,
      fontFamily = "微软雅黑",
    } = canvasConfig;

    // 开始绘制
    context.save();
    context.lineWidth = lineWidth;
    for (let i = 0; i < linePoint.length; i++) {
      // 确定线的颜色，如果没设置，则使用默认颜色
      context.save();
      if (typeof lineColor === "string") {
        !linePoint[i].linecolor && (linePoint[i].linecolor = lineColor);
      } else {
        if (lineColor.length !== linePoint.length) {
          throw new Error("线颜色数组必须与线组成的数组长度相同！");
        }
        !linePoint[i].linecolor && (linePoint[i].linecolor = lineColor[i]);
      }
      context.restore();

      if (!(linePoint[i].turnPoint && linePoint[i].turnPoint.length)) {
        // 如果没有转折点的话
        // 画线
        context.beginPath();

        context.moveTo(linePoint[i].start.x, linePoint[i].start.y);
        // 画圆形线头
        context.arc(
          linePoint[i].start.x,
          linePoint[i].start.y,
          pointSize,
          Math.atan2(
            linePoint[i].end.y - linePoint[i].start.y,
            linePoint[i].end.x - linePoint[i].start.x
          ),
          Math.atan2(
            linePoint[i].end.y - linePoint[i].start.y,
            linePoint[i].end.x - linePoint[i].start.x
          ) +
            2 * Math.PI
        );

        // 画线
        context.lineTo(linePoint[i].end.x, linePoint[i].end.y);
        context.closePath();
        context.fillStyle = linePoint[i].linecolor; // 线头填充颜色
        context.strokeStyle = linePoint[i].linecolor; // 线颜色
        context.fill();
        context.stroke();

        // 画箭头
        context.beginPath();
        // 任意方向，箭头在线指向的尾部
        // 注意，下面这个 arrowSize 就是箭头长度
        context.lineTo(
          linePoint[i].end.x -
            arrowSize *
              Math.cos(
                Math.PI / 4 -
                  Math.atan2(
                    linePoint[i].end.y - linePoint[i].start.y,
                    linePoint[i].end.x - linePoint[i].start.x
                  )
              ),
          linePoint[i].end.y +
            arrowSize *
              Math.sin(
                Math.PI / 4 -
                  Math.atan2(
                    linePoint[i].end.y - linePoint[i].start.y,
                    linePoint[i].end.x - linePoint[i].start.x
                  )
              )
        );
        context.lineTo(
          linePoint[i].end.x -
            arrowSize *
              Math.sin(
                Math.PI / 4 -
                  Math.atan2(
                    linePoint[i].end.y - linePoint[i].start.y,
                    linePoint[i].end.x - linePoint[i].start.x
                  )
              ),
          linePoint[i].end.y -
            arrowSize *
              Math.cos(
                Math.PI / 4 -
                  Math.atan2(
                    linePoint[i].end.y - linePoint[i].start.y,
                    linePoint[i].end.x - linePoint[i].start.x
                  )
              )
        );
        context.lineTo(linePoint[i].end.x, linePoint[i].end.y);
        context.closePath();
        context.fillStyle = linePoint[i].linecolor; // 箭头填充颜色
        context.fill();
        context.stroke();
        // 写字
        context.font = fontSize + "px " + fontFamily;
        context.textAlign = "center";
        context.textBaseline = "bottom";
        context.fillStyle = linePoint[i].linecolor; // 字的颜色
        // 文字出现位置
        context.save();
        // 将canvas的旋转中点更换为现在文字所在的位置
        context.translate(
          (linePoint[i].start.x + linePoint[i].end.x) / 2,
          (linePoint[i].start.y + linePoint[i].end.y) / 2
        );
        context.rotate(
          Math.atan2(
            Math.abs(linePoint[i].end.y - linePoint[i].start.y),
            Math.abs(linePoint[i].end.x - linePoint[i].start.x)
          )
        );
        context.fillText(
          linePoint[i].end.lineText ?? "",
          0, // 由于这时canvas的旋转中点已经更换，因此就直接写 0 0 就行
          textOffset, // 偏移量
          fontMaxWidth
        );
        context.restore(); // 加载默认颜色配置
      } else {
        // 如果有转折点的话
        // 画线
        context.beginPath();
        // 画圆形线头
        context.moveTo(linePoint[i].start.x, linePoint[i].start.y);
        context.arc(
          linePoint[i].start.x,
          linePoint[i].start.y,
          pointSize,
          Math.atan2(
            linePoint[i].turnPoint[0].y - linePoint[i].start.y,
            linePoint[i].turnPoint[0].x - linePoint[i].start.x
          ),
          Math.atan2(
            linePoint[i].turnPoint[0].y - linePoint[i].start.y,
            linePoint[i].turnPoint[0].x - linePoint[i].start.x
          ) +
            2 * Math.PI
        );
        context.lineTo(
          linePoint[i].turnPoint[0].x,
          linePoint[i].turnPoint[0].y
        );
        context.closePath();
        context.fillStyle = linePoint[i].linecolor; // 线头填充颜色
        context.strokeStyle = linePoint[i].linecolor; // 线颜色
        context.fill();
        context.stroke();

        // 写字
        context.font = fontSize + "px " + fontFamily;
        context.textAlign = "center";
        context.textBaseline = "bottom";
        context.fillStyle = linePoint[i].linecolor; // 字的颜色
        // 文字出现位置
        context.save(); // 保存默认状态/保存当前状态
        // 将canvas的旋转中点更换为现在文字所在的位置
        context.translate(
          (linePoint[i].start.x + linePoint[i].turnPoint[0].x) / 2,
          (linePoint[i].start.y + linePoint[i].turnPoint[0].y) / 2
        );
        context.rotate(
          Math.atan2(
            Math.abs(linePoint[i].start.y - linePoint[i].turnPoint[0].y),
            Math.abs(linePoint[i].start.x - linePoint[i].turnPoint[0].x)
          )
        );
        // 文字出现位置
        context.fillText(
          linePoint[i].turnPoint[0].lineText ?? "",
          0,
          textOffset, // 偏移量
          fontMaxWidth
        );
        context.restore(); // 加载默认颜色配置

        for (let j = 0; j < linePoint[i].turnPoint.length; j++) {
          // 转折点
          context.beginPath();
          context.moveTo(
            linePoint[i].turnPoint[j].x,
            linePoint[i].turnPoint[j].y
          );
          if (j < linePoint[i].turnPoint.length - 1) {
            context.lineTo(
              linePoint[i].turnPoint[j + 1].x,
              linePoint[i].turnPoint[j + 1].y
            );

            // 文字
            context.save();
            context.translate(
              (linePoint[i].turnPoint[j].x + linePoint[i].turnPoint[j + 1].x) /
                2,
              (linePoint[i].turnPoint[j].y + linePoint[i].turnPoint[j + 1].y) /
                2
            );
            context.rotate(
              Math.atan2(
                Math.abs(
                  linePoint[i].turnPoint[j + 1].y - linePoint[i].turnPoint[j].y
                ),
                Math.abs(
                  linePoint[i].turnPoint[j + 1].x - linePoint[i].turnPoint[j].x
                )
              )
            );
            // 文字出现位置
            context.fillText(
              linePoint[i].turnPoint[j + 1].lineText ?? "",
              0,
              textOffset, // 偏移量
              fontMaxWidth
            );
            context.restore();
          } else {
            context.save();
            context.translate(
              (linePoint[i].turnPoint[j].x + linePoint[i].end.x) / 2,
              (linePoint[i].turnPoint[j].y + linePoint[i].end.y) / 2
            );
            context.rotate(
              Math.atan2(
                Math.abs(linePoint[i].end.y - linePoint[i].turnPoint[j].y),
                Math.abs(linePoint[i].end.x - linePoint[i].turnPoint[j].x)
              )
            );
            // 文字出现位置
            context.fillText(
              linePoint[i].end.lineText ?? "",
              0,
              textOffset, // 偏移量
              fontMaxWidth
            );
            context.restore();
            context.lineTo(linePoint[i].end.x, linePoint[i].end.y);
          }
          context.closePath();
          context.stroke();
        }

        // 画箭头
        context.beginPath();
        // 任意方向，箭头在线指向的尾部
        // 注意，下面这个 arrowSize 就是箭头长度
        context.lineTo(
          linePoint[i].end.x -
            arrowSize *
              Math.cos(
                Math.PI / 4 -
                  Math.atan2(
                    linePoint[i].end.y -
                      linePoint[i].turnPoint[linePoint[i].turnPoint.length - 1]
                        .y,
                    linePoint[i].end.x -
                      linePoint[i].turnPoint[linePoint[i].turnPoint.length - 1]
                        .x
                  )
              ),
          linePoint[i].end.y +
            arrowSize *
              Math.sin(
                Math.PI / 4 -
                  Math.atan2(
                    linePoint[i].end.y -
                      linePoint[i].turnPoint[linePoint[i].turnPoint.length - 1]
                        .y,
                    linePoint[i].end.x -
                      linePoint[i].turnPoint[linePoint[i].turnPoint.length - 1]
                        .x
                  )
              )
        );
        context.lineTo(
          linePoint[i].end.x -
            arrowSize *
              Math.sin(
                Math.PI / 4 -
                  Math.atan2(
                    linePoint[i].end.y -
                      linePoint[i].turnPoint[linePoint[i].turnPoint.length - 1]
                        .y,
                    linePoint[i].end.x -
                      linePoint[i].turnPoint[linePoint[i].turnPoint.length - 1]
                        .x
                  )
              ),
          linePoint[i].end.y -
            arrowSize *
              Math.cos(
                Math.PI / 4 -
                  Math.atan2(
                    linePoint[i].end.y -
                      linePoint[i].turnPoint[linePoint[i].turnPoint.length - 1]
                        .y,
                    linePoint[i].end.x -
                      linePoint[i].turnPoint[linePoint[i].turnPoint.length - 1]
                        .x
                  )
              )
        );
        context.lineTo(linePoint[i].end.x, linePoint[i].end.y);
        context.closePath();
        context.fillStyle = linePoint[i].linecolor; // 箭头填充颜色
        context.fill();
        context.stroke();
      }
    }
    context.restore();
  }

  // 画坐标轴
  function drawCoordinate(ctx, axisFontSize = 12, axisColor = "#333") {
    const xAxis = [
      {
        x: 0 + 10,
        y: canvas.height / 2,
      },
      {
        x: canvas.width - 10,
        y: canvas.height / 2,
      },
    ];
    const yAxis = [
      {
        x: canvas.width / 2,
        y: 0 + 10,
      },
      {
        x: canvas.width / 2,
        y: canvas.height - 10,
      },
    ];
    var ary = [
      {
        start: {
          x: canvas.width - 10,
          y: canvas.height / 2,
        },
        end: {
          x: canvas.width - 10,
          y: canvas.height / 2,
          lineText: "x",
        },
      },
      {
        start: {
          x: canvas.width / 2,
          y: 0 + 10,
        },
        end: {
          x: canvas.width / 2,
          y: 0 + 10 - 0.1, // 减 0.1 是为了箭头的方向
          lineText: "y",
        },
      },
    ];
    // 坐标轴
    drawLineSegment(xAxis, axisColor);
    drawLineSegment(yAxis, axisColor);
    // 坐标箭头
    useDrawLine(ctx, ary, {
      lineColor: axisColor,
      fontSize: 30,
      pointSize: 0,
      fontFamily: "Times New Roman",
    });
    // 坐标刻度
    let xSplit = [];
    let ySplit = [];
    const split = canvas.width / 20;
    const xOffset = 20; // x 坐标轴文字相对于坐标轴的偏移量
    const yOffset = 25; // y 坐标轴文字相对于坐标轴的偏移量
    for (let x = 0; x < canvas.width; x += split) {
      const pixelX = canvas.width / 2 + x; // 像素坐标
      if (x != 0 && x != canvas.width / 2) {
        // x 轴刻度
        writeText(x, pixelX, canvas.width / 2 + xOffset, axisFontSize);
        writeText(
          -x,
          canvas.width - pixelX,
          canvas.width / 2 + xOffset,
          axisFontSize
        );
        // y 轴刻度
        writeText(-x, canvas.width / 2 - yOffset, pixelX, axisFontSize);
        writeText(
          x,
          canvas.width / 2 - yOffset,
          canvas.height - pixelX,
          axisFontSize
        );
      }
      if (x === 0 || x === canvas.width / 2) continue;
      xSplit.push([
        {
          x,
          y: canvas.height / 2,
        },
        {
          x,
          y: canvas.height / 2 + 5,
        },
      ]);
      ySplit.push([
        {
          x: canvas.width / 2,
          y: x,
        },
        {
          x: canvas.width / 2 + 5,
          y: x,
        },
      ]);
    }
    xSplit.forEach((item) => {
      drawLineSegment(item, axisColor);
    });
    ySplit.forEach((item) => {
      drawLineSegment(item, axisColor);
    });
    // 原点标注
    writeText(
      "O",
      canvas.width / 2 + 5,
      canvas.height / 2 + xOffset,
      axisFontSize + 6,
      axisColor
    );
    drawPoint(ctx, { x: canvas.width / 2, y: canvas.height / 2 }, 2, "#000");
  }

  // 绘制圆
  function drawCircle(
    ctx,
    centerPoint,
    radius,
    borderColor = "#000",
    fillColor = "transparent",
    lineWidth = 1
  ) {
    ctx.save();
    ctx.beginPath();
    ctx.fillStyle = fillColor;
    ctx.lineWidth = lineWidth;
    ctx.strokeStyle = borderColor;
    ctx.arc(centerPoint.x, centerPoint.y, radius, 0, Math.PI * 2);
    // 如果有描边那就加上 stroke 命令
    lineWidth && ctx.stroke();
    ctx.fill();
    ctx.restore();
  }

  // 绘制点
  function drawPoint(ctx, pointPosition, size = 3, color = "#000") {
    ctx.save();
    ctx.beginPath();
    ctx.fillStyle = color;
    ctx.arc(pointPosition.x, pointPosition.y, size, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
  }

  // 将实际坐标点转化为 canvas 坐标系中的点
  function transformToCanvasPoint(point) {
    return {
      x: point.x + canvas.width / 2,
      y: -point.y + canvas.height / 2,
    };
  }

  // 将 canvas 坐标系中的点转化为实际坐标点
  function transformPointBack(point) {
    return {
      x: point.x - canvas.width / 2,
      y: -point.y + canvas.height / 2,
    };
  }

  // 清理画布
  function clearCanvas() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  }

  // 边界测试
  // 返回 方位信息 或 false，false 表示未靠近或者超出边界
  function boundaryTest(point, distance = 20) {
    if (distance > canvas.width / 2 || distance > canvas.height / 2) {
      throw new Error("distance 参数超出范围！");
    }
    if (point.x <= distance) {
      if (point.y < distance) {
        return "leftTop";
      } else if (point.y > canvas.height - distance) {
        return "leftBottom";
      }
      return "left";
    }
    if (point.x >= canvas.width - distance) {
      if (point.y < distance) {
        return "rightTop";
      } else if (point.y > canvas.height - distance) {
        return "rightBottom";
      }
      return "right";
    }
    if (point.y <= distance) {
      return "top";
    }
    if (point.y >= canvas.height - distance) {
      return "bottom";
    }
    return false;
  }

  initCoordinate(ctx);
</script>
